﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VirtualTournaments.Services.Tournaments;
using VirtualTournaments.Services.Users;
using VirtualTournaments.Services.Bets;
using VirtualTournaments.DataAccess;
using VirtualTournaments.DataAccess.EntityManagers;
using VirtualTournaments.Business.Tournaments;
using VirtualTournaments.Business.Bets;
using VirtualTournaments.Business.Tournaments.Exceptions;

namespace VirtualTournaments.Services.Bets
{
    public class BetsService
    {

        /// Obtiene las apuestas sobre una partido
        public static TorunamentTeamOdds[] GetTorunamentsTeamOdds(Services.Tournaments.Tournament tournament)
        {
            ITournamentsManager tournamentData = DataAccessManager.EntityManagersFactory.GetTournamentsManager();
            IBetTypesManager betTypesData = DataAccessManager.EntityManagersFactory.GetBetTypesManager();

            Business.Tournaments.Tournament bizTournament = tournamentData.GetByExactName(tournament.Name);

            List<TorunamentTeamOdds> result = new List<TorunamentTeamOdds>();
            try
            {
                foreach (BetType eachBetType in betTypesData.GetByCompetition(bizTournament))
                {
                    if (eachBetType == null)
                    {
                        throw new CompetitionNotRegisteredException(bizTournament.ToString());
                    }
                    if (eachBetType is TournamentPodiumBetType)
                    {
                        TournamentPodiumBetType tournamentBetType = (TournamentPodiumBetType)eachBetType;
                        
                        foreach (var team in bizTournament.Teams)
                        {
                            TorunamentTeamOdds matchOdds = new TorunamentTeamOdds(new Services.Tournaments.Team(team.Name));
                            matchOdds.OddsForFirst = tournamentBetType.getResultByWinningSimplePosition(team.ToString(), Position.First).Odds;
                            matchOdds.OddsForSecond = tournamentBetType.getResultByWinningSimplePosition(team.ToString(), Position.Second).Odds;
                            matchOdds.OddsForThird = tournamentBetType.getResultByWinningSimplePosition(team.ToString(), Position.Third).Odds;
                            result.Add(matchOdds); 	 
                        }
                    }
                }
            }
            catch (DataAccess.Exceptions.DataAccessException ex)
            {
                throw new ServiceException(ex.ToString());
            }
            return result.ToArray(); ;
        }


        /// <summary>
        /// Obtiene las apuestas sobre una jornada
        /// </summary>
        /// <param name="journey"></param>
        /// <returns></returns>
        public static JourneyMatchOdds[] GetTorunamentNextJourneyMatchOdds(Services.Tournaments.Tournament tournament)
        {
			List<JourneyMatchOdds> result = new List<JourneyMatchOdds>();

            ITournamentsManager tournamentData = DataAccessManager.EntityManagersFactory.GetTournamentsManager();
            IBetTypesManager betTypesData = DataAccessManager.EntityManagersFactory.GetBetTypesManager();
			Business.Tournaments.Tournament bizTournament = tournamentData.GetByExactName(tournament.Name);
			try
			{ 
				Business.Tournaments.Journey bizJourney = bizTournament.GetNextJourney();
				foreach (var match in bizJourney)
				{
					try
					{

						foreach (ThreeWaysBetType eachBetType in betTypesData.GetByCompetition(match))
						{
							if (eachBetType == null)
							{
								throw new CompetitionNotRegisteredException(match.ToString());
							}
							if (eachBetType is ThreeWaysBetType)
							{
								ThreeWaysBetType threeWaysBetType = (ThreeWaysBetType)eachBetType;

								JourneyMatchOdds matchOdds = new JourneyMatchOdds();
								matchOdds.OddsForHome = threeWaysBetType.GetResultByWinningTeam(MatchWinningTeam.Local).Odds;
								matchOdds.OddsForDraw = threeWaysBetType.GetResultByWinningTeam(MatchWinningTeam.Draw).Odds;
								matchOdds.OddsForAway = threeWaysBetType.GetResultByWinningTeam(MatchWinningTeam.Visitor).Odds;

								matchOdds.HomeTeam = new Services.Tournaments.Team(match.Local.Name);
								matchOdds.AwayTeam = new Services.Tournaments.Team(match.Visitor.Name);
								result.Add(matchOdds);
							}
						}

					}
					catch (DataAccess.Exceptions.DataAccessException ex)
					{
						throw new ServiceException(ex.ToString());
					}
				}
			}
			catch (FinishedTournamentException)
			{
				//en caso de torneo finalizado no hay partidos para apostar
			}

            
            return result.ToArray();
        }

        /// <summary>
        /// Sirve para apostar por un partido
        /// </summary>
        /// <param name="tournament"></param>
        /// <param name="user"></param>
        /// <param name="wageredMoney"></param>
        /// <param name="expectedResult"></param>
        public static void BetMatch(Services.Tournaments.Tournament servTournament, Services.Tournaments.Match servMatch, Services.Users.User servUser, decimal wageredMoney, MatchResultEnum servExpectedResult)
        {
            IBetTypesManager dataManager = DataAccessManager.EntityManagersFactory.GetBetTypesManager();
            IMatchesManager matchesData = DataAccessManager.EntityManagersFactory.GetMatchesManager();
            ITournamentsManager tournamentsData = DataAccessManager.EntityManagersFactory.GetTournamentsManager();
            ITeamsManager teamsData = DataAccessManager.EntityManagersFactory.GetTeamsManager();
            IUsersManager userData = DataAccessManager.EntityManagersFactory.GetUsersManager();

            VirtualTournaments.Business.Tournaments.Tournament bizTournament = tournamentsData.GetByExactName(servTournament.Name);
            VirtualTournaments.Business.Tournaments.Team bizLocal = teamsData.GetByName(servMatch.Local);
            VirtualTournaments.Business.Tournaments.Team bizVisitor = teamsData.GetByName(servMatch.Visitor);
            Business.Users.User bizUser = userData.GetByUserName(servUser.name);
            Business.Tournaments.Match bizMatch = matchesData.GetByTournamentAndLocalTeamAndVisitorTeam(bizTournament, bizLocal, bizVisitor);
            MatchWinningTeam bizExpectedResult = MatchResultEnumServiceBusinessConversor.Convert(servExpectedResult);

            Agency agency = LoadAgencyWithCompetitions(new ICompetition[] { bizMatch }, dataManager);
            BetType betType = agency.GetBetTypeByCompetition(bizMatch);
            if (betType == null) {
                throw new CompetitionNotRegisteredException(bizMatch.ToString());
            }
            if (betType is ThreeWaysBetType)
            {
                ThreeWaysBetType threeWaysBetType = (ThreeWaysBetType)betType;
                BetTarget target = threeWaysBetType.GetResultByWinningTeam(bizExpectedResult);
                agency.Bet(bizUser, target, wageredMoney);
                Save(dataManager, agency);
            }else {
                throw new ServiceException("El tipo de apuesta para esta competicion es incompatible con el tipo de apuesta que desea realizar. Se esperaba ThreeWaysBetType pero vino " + betType.GetType().ToString());
            }
        }

        /// <summary>
        /// Sirve para apostar al resultado de un torneo
        /// </summary>
        public static void BetTournament(Services.Tournaments.Tournament servTournament, Services.Users.User servUser, decimal wageredMoney, Services.Tournaments.Team servTeam, TournamentPositionEnum servFirstTeam)
        {
            IBetTypesManager dataManager = DataAccessManager.EntityManagersFactory.GetBetTypesManager(); 
            ITournamentsManager tournamentsData = DataAccessManager.EntityManagersFactory.GetTournamentsManager();
            ITeamsManager teamsData = DataAccessManager.EntityManagersFactory.GetTeamsManager();
            IUsersManager userData = DataAccessManager.EntityManagersFactory.GetUsersManager();

            VirtualTournaments.Business.Tournaments.Tournament bizTournament = tournamentsData.GetByExactName(servTournament.Name);
            VirtualTournaments.Business.Tournaments.Team bizTeam = teamsData.GetByName(servTeam.Name);
            Business.Users.User bizUser = userData.GetByUserName(servUser.name);
            Position bizPosition = TournamentPositionEnumServicesBusintessConversor.Convert(servFirstTeam);

            Agency agency = LoadAgencyWithCompetitions(new ICompetition[] { bizTournament }, dataManager);

            BetType betType = agency.GetBetTypeByCompetition(bizTournament);
            if (betType == null)
            {
                throw new CompetitionNotRegisteredException(bizTournament.ToString());
            }
            if (betType is TournamentPodiumBetType)
            {
                TournamentPodiumBetType tournamentPodiumBetType = (TournamentPodiumBetType)betType;
                BetTarget target = tournamentPodiumBetType.getResultByWinningSimplePosition(bizTeam.Name, bizPosition);
                agency.Bet(bizUser, target, wageredMoney);
                Save(dataManager, agency);
            }
            else
            {
                throw new ServiceException("El tipo de apuesta para esta competicion es incompatible con el tipo de apuesta que desea realizar. Se esperaba TournamentPodiumBetType pero vino " + betType.GetType().ToString());
            }
        }

        /// <summary>
        /// Registra un torneo como apostable
        /// </summary>
        /// <param name="tournament"></param>
        internal static void RegisterTournament(VirtualTournaments.Business.Tournaments.Tournament bizTournament)
        {
            RegisterCompetitions(new ICompetition[] { bizTournament });
        }

        /// <summary>
        /// Registra como apostables los partidos de una fecha determinada.
        /// </summary>
        internal static void RegisterJourneysMatches(VirtualTournaments.Business.Tournaments.Journey bizJourney)
        {
            //casteo a queriable de ICompetition
            var competitions = from match in bizJourney
                               select ((ICompetition)match);

            RegisterCompetitions(competitions.ToArray());
        }

        /// <summary>
        /// Registra competiciones como apostables
        /// </summary>
        /// <param name="competitions"></param>
        private static void RegisterCompetitions(ICompetition[] bizCompetitions)
        {
            IBetTypesManager DataManager = DataAccessManager.EntityManagersFactory.GetBetTypesManager();

            Agency agency = CreateAgency();
            foreach (var competition in bizCompetitions)
            {
                if (DataManager.GetByCompetition(competition).Count()>0)
                {
                    throw new CompetitionAlreadyRegisteredException(competition);
                }
                agency.AddCompetition(competition);
            }

            Save(DataManager, agency);
        }

        /// <summary>
        /// Guarda la Agencia, es decir, todos los BetTypes que contiene
        /// </summary>
        /// <param name="DataManager"></param>
        /// <param name="agency"></param>
        private static void Save(IBetTypesManager DataManager, Agency bizAgency)
        {
            try
            {
                foreach (var betType in bizAgency.AvailableBetTypes)
                {
                    DataManager.Save(betType);
                }
            }
            catch (VirtualTournaments.DataAccess.Exceptions.DataAccessException ex)
            {
                throw new ServiceException("No se puede guardar algun BetType: " + ex.ToString());
            }
        }

        private static Agency CreateAgency()
        {
            return new Agency(5, new Business.Bets.ConcreteBetTypeFactory());
        }

        /// <summary>
        /// Liquida las apuestas si las hubiere correspondientes a los partidos finalizados
        /// </summary>
        /// <param name="matches"></param>
        internal static void NotifyFinishedMatches(VirtualTournaments.Business.Tournaments.Match[] bizMatches)
        {
            var competitions = from match in bizMatches
                               select (ICompetition)match;

            NotifyCompetitionsFinished(competitions);
        }

        private static void NotifyCompetitionsFinished(IEnumerable<ICompetition> bizCompetitions)
        {

            IBetTypesManager DataManager = DataAccessManager.EntityManagersFactory.GetBetTypesManager();

            Agency agency = LoadAgencyWithCompetitions(bizCompetitions, DataManager);

            foreach (var competition in bizCompetitions)
            {
                agency.NotifyCompetitionFinished(competition);
            }

            Save(DataManager, agency);
        }

        /// <summary>
        /// Crea una agencia y le carga de la base las competiciones solicitadas por parámetro
        /// </summary>
        /// <param name="competitions"></param>
        /// <param name="DataManager"></param>
        /// <returns></returns>
        private static Agency LoadAgencyWithCompetitions(IEnumerable<ICompetition> bizCompetitions, IBetTypesManager DataManager)
        {
            Agency agency = CreateAgency();
            foreach (var competition in bizCompetitions)
            {
                foreach (var bettype in DataManager.GetByCompetition(competition))
                {
                    agency.AddBetType(bettype);
                }
            }
            return agency;
        }

        /// <summary>
        /// Liquida las apuestas si las hubiere correspondientes al torneo finalizado
        /// </summary>
        /// <param name="bizTournament"></param>
        internal static void NotifyFinishedTournament(VirtualTournaments.Business.Tournaments.Tournament bizTournament)
        {
            NotifyCompetitionsFinished(new ICompetition[] { bizTournament });
        }
    }
}
